Skip to content

fix: global concurrency cap for hook-triggered indexers - #160

Open
rajkumarsakthivel wants to merge 5 commits into
mainfrom
fix/hook-concurrency-cap
Open

fix: global concurrency cap for hook-triggered indexers#160
rajkumarsakthivel wants to merge 5 commits into
mainfrom
fix/hook-concurrency-cap

Conversation

@rajkumarsakthivel

Copy link
Copy Markdown
Member

Summary

Fixes #159. Git hooks fire in all worktrees (shared .git/hooks/), and the old hook spawned unbounded background indexers. With N worktrees this produced N detached cce index processes.

  • Global machine-wide lock via mkdir (POSIX portable, works on macOS without flock). At most one hook-triggered indexer runs at a time. Stale locks are reclaimed when the owner PID is dead.
  • Skip ephemeral worktree paths (/tmp/*, .claude/worktrees/*) since agent-created throwaway trees are deleted minutes later.
  • nice -n 10 so indexing never competes with foreground work.
  • Worktree-aware hook install: install_hooks() resolves through git rev-parse --git-common-dir so hooks install correctly from inside a worktree.
  • Upgrade path: cce init replaces old single-line hook blocks with the new guarded version instead of silently skipping when the marker is present.
  • Uninstall: handles multi-line hook blocks via start/end markers, backward-compatible with old single-line format.

Warm zinc-black palette, indigo accents, Inter+Outfit typography.
Massive 94% number as hero focal point, trust-strip with editor logos,
vertical numbered capabilities, horizontal benchmark stats, and
comparison table with green "Best" pill.
Git hooks installed by CCE fire in all worktrees (shared .git/hooks/),
and the old hook spawned unbounded background indexers. With N worktrees
this produced N detached cce-index processes (observed: 36 at ~1.85GB
each, ~66GB total on a 96GB machine).

Three changes:
- Global machine-wide lock via mkdir (POSIX portable, works on macOS
  without flock). At most one hook-triggered indexer runs at a time.
- Skip ephemeral worktree paths (/tmp/*, .claude/worktrees/*) since
  those trees are deleted minutes later.
- nice -n 10 so indexing never competes with foreground work.

Also fixes:
- install_hooks() now resolves through git common dir so hooks install
  correctly from inside a worktree.
- cce init/upgrade replaces old hook blocks with the new guarded version
  instead of silently skipping when the marker is present.
- Uninstall handles multi-line hook blocks (start/end markers).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses runaway resource usage from hook-triggered cce index when Git worktrees share a common hooks directory, by making hook installation worktree-aware and adding guardrails to the generated hook script.

Changes:

  • Adds a guarded, background hook script that attempts to serialize hook-triggered indexing via a global lock and deprioritizes indexing with nice.
  • Resolves hooks installation through git rev-parse --git-common-dir so installing from within a worktree writes to the effective hooks directory.
  • Updates hook-uninstall stripping logic to support the new multi-line hook block format (marker + end-marker), while remaining backward-compatible with the legacy single-line format.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/context_engine/indexer/git_hooks.py Generates new guarded hook script and installs hooks into the correct common hooks dir for worktrees; adds upgrade behavior when an existing marker is found.
src/context_engine/cli.py Updates uninstall hook-block stripping to handle the new multi-line hook block format.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +63 to +67
# Build the ephemeral-path skip check as shell conditions.
skip_checks = " || ".join(
f'case "$PWD" in *{shlex.quote(m)}*) true;; *) false;; esac'
for m in _EPHEMERAL_PATH_MARKERS
)
Comment thread src/context_engine/indexer/git_hooks.py Outdated
Comment on lines +12 to +16
# Max concurrent hook-triggered indexers machine-wide. The hook script
# uses a global lock file (not per-project) so worktrees sharing
# .git/hooks/ don't each spawn their own unbounded indexer. See #159.
_MAX_CONCURRENT_INDEXERS = 2

Comment thread src/context_engine/cli.py Outdated
Comment on lines +2409 to +2412
# Old format (no end marker): skip just the one line after marker
if not any(HOOK_END_MARKER in ln for ln in lines):
inside_block = False
continue
Comment on lines +161 to +165
marker_idx = existing.index(HOOK_MARKER)
prefix = existing[:marker_idx].rstrip()
new_content = prefix + ("\n\n" if prefix else "#!/bin/sh\n\n") + script
hook_path.write_text(new_content, encoding="utf-8")
hook_path.chmod(hook_path.stat().st_mode | stat.S_IEXEC)
- Remove shlex.quote from case glob patterns (broke ephemeral path matching)
- Remove unused _MAX_CONCURRENT_INDEXERS constant
- Fix _strip_cce_git_hook_block to detect end marker only after start marker
- Fix reinstall to preserve user content after the CCE block
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Git hooks fire for every worktree with no concurrency cap — 36 detached indexers, ~66GB RAM, host unusable

2 participants